/*
    1.抽象日志落地基类
    2.派生子类（根据不同的落地方式派生出子类）
    3.通过工厂模式生产日志落地子类
*/

#ifndef __M_SINK_H__
#define __M_SINK_H__

#include <iostream>
#include <string>
#include <sstream>
#include <fstream>
#include <memory>
#include <cassert>
#include <ctime>
#include "util.hpp"


namespace mylog{
    // 1.日志落地基类——抽象类 用于提供落地实现接口
    class LogSink{
    public:
        using ptr = std::shared_ptr<LogSink>;

        LogSink() {}
        virtual ~LogSink() {}
        virtual void log(const char *data, const size_t len) = 0;
        virtual void flushbuffer(){}  // 提供一个刷新缓冲区的空实现 由派生类根据特殊需求来进行重写
    };

    // 2.根据不同的日志落地方式派生子类
    // 标准输出
    class StdcoutSink : public LogSink {
    public:
        void log(const char *data, const size_t len){
            std::cout.write(data, len);
        }
    };

    // 文件输出
    class FileSink : public LogSink {
    public:
        FileSink(const std::string &filename)
        :_filename(filename)
        {
            // 先创建当前文件所在的目录 创建之前会先判断是否存在
            mylog::util::File::createdirectory(mylog::util::File::path(_filename));
            // 打开目标文件 以二进制+追加方式打开
            _ofs.open(_filename, std::ios::binary | std::ios::app);
            assert(_ofs.is_open());
        }

        void log(const char *data, const size_t len){
            _ofs.write(data, len);
        }

        void flushbuffer(){
            _ofs.flush();
        }

        ~FileSink(){
            if(_ofs.is_open()){
                _ofs.close();
            }
        }

    private:
        std::string _filename;
        std::ofstream _ofs;
    };

    // 滚动文件输出
    class RollSink : public LogSink {
    public:
        RollSink(const std::string &filename, const size_t max_size)
        :_basename(filename),
        _max_size(max_size),
        _cur_size(0),
        _count(0)
        {
            // 创建当前文件所在的目录 创建之前会先判断是否存在
            mylog::util::File::createdirectory(mylog::util::File::path(_basename));
        }

        ~RollSink(){
            if(_ofs.is_open()){
                _ofs.close();
            }
        }

        void log(const char *data, const size_t len){
            InitFile(len);
            _ofs.write(data, len);
            _cur_size += len;
        }

        void flushbuffer(){
            _ofs.flush();
        }

    private:
        // 在基础文件名上+时间格式 生成新的文件名
        std::string createfilename(){
            struct tm t;
            time_t ctime = mylog::util::Data::now();
            localtime_r(&ctime, &t);
            std::stringstream ss;
            ss << _basename;
            // 在当前文件名基础上加上对应的时间作为标志
            ss << "-";
            ss << (t.tm_year + 1900) << (t.tm_mon + 1) << t.tm_mday << t.tm_hour << t.tm_min << t.tm_sec;
            ss << "-" << _count << ".log";
            return ss.str();
        }

        // 初始化文件 判断文件是否打开 以及是否需要重新打开一个文件
        void InitFile(const size_t len){
            std::string filename = createfilename();
            if (_ofs.is_open() == false || _cur_size + len > _max_size){
                _ofs.close();
                _ofs.open(filename, std::ios::binary | std::ios::app);
                assert(_ofs.is_open());
                _cur_size = 0;
                _count++;
            }
        }  
 
    private:
        std::string _basename;     // 基础文件名
        std::ofstream _ofs;        // 文件句柄
        size_t _max_size;          // 一个文件允许写入的最大字节数
        size_t _cur_size;          // 文件当前大小
        size_t _count;             // 文件编号
    };

    // 生产日志落地子类的工厂类
    class SinkFactory{
    public:
        // 利用可变参数模板+右值引用+完美转发 解决不同的子类传递不同的参数
        template<typename SinkType, typename... Args>
        static LogSink::ptr create(Args &&... args){
            return std::make_shared<SinkType>(std::forward<Args>(args)...);
        }
    };
}

#endif